/******************************************************************************
 * Copyright 2022 The AIR Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#pragma once

#include <string>
#include "asn1-operator.hpp"

template <asn_TYPE_descriptor_t const *descriptor, typename T>  //
struct asn1_holder_base {};

template <asn_TYPE_descriptor_t const *descriptor, typename T>  //
struct asn1_holder : public asn1_holder_base<descriptor, T> {};

template <asn_TYPE_descriptor_t const *descriptor>  //
struct asn1_holder<descriptor, BIT_STRING_t>
    : public asn1_holder_base<descriptor, BIT_STRING_t> {
 protected:
  BIT_STRING_t *const _m_holder;
  const BIT_STRING_t *const _m_holder_const;

 public:
  explicit asn1_holder(BIT_STRING_t *p) : _m_holder(p), _m_holder_const(p) {}
  explicit asn1_holder(const BIT_STRING_t *p)
      : _m_holder(nullptr), _m_holder_const(p) {}
  bool init(size_t size = 8) {
    if (!_m_holder) {
      LOG(ERROR) << "not init or immutable";
      return false;
    }
    if (_m_holder->buf) {
      free(_m_holder->buf);
    }
    _m_holder->buf = static_cast<uint8_t *>(calloc(1, size));
    if (!_m_holder->buf) {
      return false;
    }
    _m_holder->size = 8;
    return true;
  }
  bool test_bit(size_t bit) const {
    if (!_m_holder_const) {
      LOG(ERROR) << "not init";
      return false;
    }
    size_t idx_byte = bit >> 3;
    bit = 7 - (bit & 7);
    if (idx_byte >= _m_holder_const->size) {
      return false;
    }
    return 0 != (_m_holder_const->buf[idx_byte] & (1U << bit));
  }
  bool set_bit(size_t bit, bool val = true) {
    if (!_m_holder) {
      LOG(ERROR) << "not init or immutable";
      return false;
    }
    size_t idx_byte = bit >> 3;
    bit = 7 - (bit & 7);
    if (idx_byte >= _m_holder->size) {
      return false;
    }
    if (val) {
      _m_holder->buf[idx_byte] |= (1U << bit);
    } else {
      _m_holder->buf[idx_byte] &= ~(1U << bit);
    }
    return true;
  }
  bool reset_bit(size_t bit) { return set_bit(bit, false); }
  std::string to_string() const {
    if (!_m_holder_const) {
      LOG(ERROR) << "not init";
      return "";
    }
    std::string result = "BIT_STR (" + std::to_string(_m_holder_const->size) +
                         " * 8 - " +
                         std::to_string(_m_holder_const->bits_unused) + ")";
    for (size_t loc = 0; loc < _m_holder_const->size; loc++) {
      result += " ";
      unsigned char c = _m_holder_const->buf[loc];
      for (int i = 0; i < 8; i++) {
        result += (0x80U & c) ? "1" : "0";
        c <<= 1;
      }
    }
    for (int i = 0; i < _m_holder_const->bits_unused; i++) {
      if (!result.empty()) {
        result.pop_back();
      }
    }
    return result;
  }
};

#define ASN1_HOLDER(type) asn1_holder<&asn_DEF_##type, type##_t>
